package cn.kivensoft.util;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public final class BeanUtils {
	private BeanUtils() {}

	/** JavaBean中的getXXX/setXXX转成字段名称
	 * 字段名转get/set的特殊命名规则:
	 *   1. 第二个字符大写, 则名称不变
	 *   2. 前两个字符大写, 则名称不变
	 * @param methodName 函数名称
	 * @return 字段名称
	 */
	public static String nameOfMethod(String methodName) {
		int len = methodName.length();
		char[] tmp = new char[len - 3];
		char c = methodName.charAt(3);
		boolean firstUpper = c >= 'A' && c <= 'Z';
		// 首字符是大写字符且长度为1或者第二个字符不是大写, 则转换首字符为小写, 其它则不变
		if (firstUpper) {
			if (len == 4) c = (char) (c + 0x20);
			else {
				char c2 = methodName.charAt(4);
				if (c2 < 'A' || c2 > 'Z') c = (char) (c + 0x20);
			}
		}
		tmp[0] = c;
		methodName.getChars(4, len, tmp, 1);
		return new String(tmp);
	}

	/** JavaBean中的字段名转成getXXX/setXXX
	 * @param fieldName 字段名
	 * @param isGet true: getXXX, false: setXXX
	 * @return 存取字段的函数名称
	 */
	public static String methodOfName(String fieldName, boolean isGet) {
		int len = fieldName.length();
		char[] tmp = new char[len + 3];
		tmp[0] = isGet ? 'g' : 's';
		tmp[1] = 'e';
		tmp[2] = 't';

		char c = fieldName.charAt(0);
		// 首字符是小写, 需要深入判断
		if (c >= 'a' && c <= 'z') {
			// 第二个字符不是大写, 则首字符改为大写, 其它不变
			if (len > 1) {
				char c2 = fieldName.charAt(1);
				if (c2 < 'A' || c2 > 'Z') c = (char) (c - 0x20);
			}
			// 长度为1时需要改为大写
			else
				c = (char) (c - 0x20);
		}
		tmp[3] = c;
		fieldName.getChars(1, len, tmp, 4);
		return new String(tmp);
	}

	public static class FieldMethod {
		public final Field field;
		public final Method get;
		public final Method set;

		public FieldMethod(Field field, Method get, Method set) {
			this.field = field;
			this.get = get;
			this.set = set;
		}
	}

	/**
	 * 解析类，获取类的字段读写方法，如果需要频繁多次调用，建议使用本方法缓存，避免反射带来的性能问题
	 * @param clazz 类
	 * @return 字段读写方法映射
	 */
	public static Map<String, FieldMethod> parseObjectMethods(Class<?> clazz) {
		HashMap<String, FieldMethod> ret = new HashMap<>();
		FieldMethod empty = new FieldMethod(null, null, null);

		Method[] methods = clazz.getMethods();
		AccessibleObject.setAccessible(methods, true);
		for (Method method : methods) {
			String name = method.getName();
			if (name.length() < 4 || name.equals("getClass") || !name.startsWith("get")
					|| method.getParameterCount() > 0 || method.getReturnType() == void.class)
				continue;

			ret.put(nameOfMethod(name), new FieldMethod(null, method, null));
		}

		Field[] fields = clazz.getFields();
		AccessibleObject.setAccessible(fields, true);
		for (Field field : fields) {
			String name = field.getName();
			FieldMethod fm = ret.get(name);
			if (fm == null)
				fm = empty;
			ret.put(name, new FieldMethod(field, fm.get, null));
		}

		for (Method method : methods) {
			String name = method.getName();
			if (name.length() < 4 || !name.startsWith("set") || method.getParameterCount() != 1
					|| method.getReturnType() != void.class)
				continue;

			String fname = nameOfMethod(name);
			FieldMethod fm = ret.get(fname);
			if (fm == null)
				fm = empty;
			ret.put(fname, new FieldMethod(fm.field, fm.get, method));
		}

		return ret;
	}

	/** javabean的属性复制(值不为null时复制)，只支持同名同类型属性的复制
	 * @param src 复制源对象
	 * @param dst 复制的目标对象
	 */
	public static <T> T copyObjProps(Object src, T dst) {
		if (src == null || dst == null)
			return dst;

		HashMap<String, Object> map = objectToHashMap(src);
		mapToObject(map, dst);
		return dst;
	}

	/** Object转HashMap
	 *
	 * @param src 源对象
	 * @return 新的hashmap
	 */
	@SuppressWarnings("unchecked")
	public static HashMap<String, Object> objectToHashMap(Object src) {
		if (src instanceof HashMap) {
			HashMap<?, ?> map = (HashMap<?, ?>) src;
			Type mapType = map.getClass().getGenericSuperclass();
			if (mapType instanceof ParameterizedType) {
				ParameterizedType parameterizedType = (ParameterizedType) mapType;
				Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
				// 检查第一个类型参数是否为String.class
				if (actualTypeArguments.length > 0 && actualTypeArguments[0].equals(String.class)) {
					// 注意：这里我们不能检查第二个类型参数是否为Object.class，因为所有类都是Object的子类
					// 并且由于泛型擦除，我们无法获取到确切的泛型类型信息（除了键的类型，它在这里是通过超类的泛型信息间接获取的）
					return (HashMap<String, Object>) src;
				}
			}
		}

		HashMap<String, Object> ret = new HashMap<>();

		if (src instanceof Map) {
			Map<?, ?> map = (Map<?, ?>) src;
			for (Map.Entry<?, ?> entry : map.entrySet())
				ret.put(entry.getKey().toString(), entry.getValue());
			return ret;
		}

		if (src != null)
			getFields(src, (name, value, cls) -> {
				if (value != null)
					ret.put(name, value);
				return true;
			});
		return ret;
	}

	/** Object转map
	 *
	 * @param src 源对象
	 * @param dst 目标对象
	 * @return dst
	 */
	public static Map<String, Object> objToMap(Object src, Map<String, Object> dst) {
		if (src instanceof Map) {
			Map<?, ?> map = (Map<?, ?>) src;
			for (Map.Entry<?, ?> entry : map.entrySet())
				dst.put(entry.getKey().toString(), entry.getValue());
		} else if (src != null) {
			getFields(src, (name, value, cls) -> {
				if (value != null)
					dst.put(name, value);
				return true;
			});
		}
		return dst;
	}

	/** map转Object
	 *
	 * @param src 源
	 * @param dst 目标
	 * @return dst
	 */
	public static <T> T mapToObject(Map<String, Object> src, T dst) {
		if (src != null)
			setFields(dst, (name, cls) -> src.get(name));
		return dst;
	}

	public interface GetFieldConsumer {
		boolean accept(String name, Object value, Class<?> valueClass) throws Exception;
	}

	/** 循环对象的所有字段名/值
	 * @param obj 要循环的对象
	 * @param func 回调函数, 每个找到的字段名/值进行回调(name, value, valueClass)
	 */
	public static void getFields(Object obj, GetFieldConsumer func) {
		if (obj == null)
			return;

		// 先查找所有getXXX函数, 然后回调GetFieldConsumer接口
		HashSet<String> names = new HashSet<>();
		Method[] methods = obj.getClass().getMethods();
		AccessibleObject.setAccessible(methods, true);
		for (int i = 0, n = methods.length; i < n;) {
			try {
				while (i < n) {
					Method m = methods[i++];
					String name = m.getName();
					if (name.length() < 4 || name.equals("getClass")
							|| !name.startsWith("get") || m.getParameterCount() > 0
							|| m.getReturnType() == void.class)
						continue;
					String fname = nameOfMethod(name);
					if (!func.accept(fname, m.invoke(obj), m.getReturnType())) {
						return;
					}
					names.add(fname); // 字段名加入已访问字段缓存
				}
			} catch (Exception ignored) {}
		}

		// 接下来处理公共存取字段, 忽略已被getXXX处理过的字段
		Field[] fields = obj.getClass().getFields();
		AccessibleObject.setAccessible(fields, true);
		for (int i = 0, n = fields.length; i < n;) {
			try {
				while (i < n) {
					Field f = fields[i++];
					String name = f.getName();
					if (!names.contains(name)) // 字段没有被处理过
						if (!func.accept(name, f.get(obj), f.getType())) {
							return;
						}
				}
			} catch (Exception ignored) { }
		}
	}

	public interface SetFieldFunction {
		Object apply(String name, Class<?> valueClass) throws Exception;
	}

	/** 循环对象的所有字段名/值
	 * @param obj 要循环的对象
	 * @param func 回调函数, 每个找到的字段名/值进行回调(name, valueClass)
	 */
	public static void setFields(Object obj, SetFieldFunction func) {
		if (obj == null) return;

		// 先查找所有setXXX函数, 然后回调SetFieldConsumer接口
		HashSet<String> names = new HashSet<>();
		Method[] methods = obj.getClass().getMethods();
		AccessibleObject.setAccessible(methods, true);
		for (int i = 0, n = methods.length; i < n;) {
			try {
				while (i < n) {
					Method m = methods[i++];
					String name = m.getName();
					if (name.length() < 4 || !name.startsWith("set") || m.getParameterCount() != 1
							|| m.getReturnType() != void.class)
						continue;
					String fname = nameOfMethod(name);
					Object val = func.apply(fname, m.getParameterTypes()[0]);
					if (val != null)
						m.invoke(obj, val);
					names.add(fname); // 字段名加入缓存
				}
			} catch (Exception ignored) {}
		}

		// 接下来处理公共存取字段, 忽略已被getXXX处理过的字段
		Field[] fields = obj.getClass().getFields();
		AccessibleObject.setAccessible(fields, true);
		for (int i = 0, n = fields.length; i < n;) {
			try {
				while (i < n) {
					Field f = fields[i++];
					String name = f.getName();
					if (!names.contains(name)) { // 字段没有被处理过
						Object val = func.apply(name, f.getType());
						if (val != null)
							f.set(obj, val);
					}
				}
			} catch (Exception ignored) { }
		}
	}

	/** 获取对象指定字段名的值
	 * @param obj 对象实例
	 * @param name 字段名
	 * @return 字段值
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getField(Object obj, String name) {
		Class<?> cls = obj.getClass();
		try {
			Method m = cls.getMethod(methodOfName(name, true));
			m.setAccessible(true);
			return (T) m.invoke(obj);
		} catch (Exception e) {
			try {
				Field f = cls.getField(name);
				f.setAccessible(true);
				return (T) f.get(obj);
			} catch (Exception e1) {
				return null;
			}
		}
	}

	/** 设置对象指定字段的值
	 * @param obj 对象实例
	 * @param name 字段名
	 * @param value 值, 不能为null, 否则将引发空值异常
	 * @return 设置是否成功
	 */
	public static boolean setField(Object obj, String name, Object value) {
		return setField(obj, name, value, value.getClass());
	}

	/** 设置对象指定字段的值
	 * @param obj 对象实例
	 * @param name 字段名
	 * @param value 值
	 * @param valueClass value的类型
	 * @return 设置是否成功
	 */
	public static boolean setField(Object obj, String name, Object value, Class<?> valueClass) {
		Class<?> cls = obj.getClass();
		try {
			Method m = cls.getMethod(methodOfName(name, false), valueClass);
			m.setAccessible(true);
			m.invoke(obj, value);
		} catch (Exception e) {
			try {
				Field f = cls.getField(name);
				f.setAccessible(true);
				f.set(obj, value);
			} catch (Exception e1) {
				return false;
			}
		}
		return true;
	}

}
